package com.dirven.game;

import java.util.ArrayList;
import java.util.LinkedList;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.dirven.game.sprite.*;
import com.dirven.game.wall.WallGlow;

public class Grid {
	
	private byte setting;
	
	public static int rows,
					  cols;
	
	public static int cellWidth,
					  cellHeight;
	
	public final static byte SETTING_STATION = 0,
							 SETTING_TUNNEL = 1;
	
	public final static byte WALL_NORTH = 0,
			 				 WALL_SOUTH = 1,
			 				 WALL_EAST = 2,
			 				 WALL_WEST = 3;
	
	private static byte blockedWalls;
	
	public static boolean wallNorthBlocked = true,
						  wallSouthBlocked = true,
						  wallEastBlocked = true,
						  wallWestBlocked = true;
	
	private Level level;
	
	private long lastUpdated = 0; // The time when the grid was last updated.
	
	private Player player;
	
	public static ArrayList<GridSprite> sprites;
	
	private Laser laser = new Laser((float)DirVenGame.screenHeight/2, (float)DirVenGame.screenHeight-16, Sprite.DIRECTION_SOUTH);
	
	int newX = 0;

	private int recursive;
	
	/**
	 * 
	 * Creates the grid for level 1.
	 * 
	 * @param cellWidth
	 * @param cellHeight
	 */
	public Grid(int cellWidth, int cellHeight) {
		Grid.cellWidth = cellWidth;
		Grid.cellHeight = cellHeight;

		Grid.cols = DirVenGame.screenWidth/cellWidth;
		Grid.rows = DirVenGame.screenHeight/cellHeight;
		
		reset();
	}
	
	public void reset() {
		Gdx.app.log("DIRVEN", "Reset grid");
		setting = SETTING_STATION;
		//blockAllWalls();
		blockAllWallsExcept(WALL_NORTH);
		
		if(sprites == null) {
			sprites = new ArrayList<GridSprite>();
		} else {
			sprites.clear();
		}
		
		if(level == null) {
			level = new Level();
		}
		
		level.setupLevel(1);
		this.setupLevel();
		
		player.setDirection(GridSprite.DIRECTION_EAST);
	}
	
	/**
	 * Get the cell's position in pixels.
	 * 
	 * @param x
	 * @param y
	 * @return coordinate of the cell (in pixels)
	 */
	public static Coord getPixelPositionOfCell(int x, int y) {
		return new Coord((float)(x * cellWidth), (float)(y * cellHeight));
	}
	
	/**
	 * Get the number of rows in the grid.
	 * 
	 * @return
	 */
	public static int getNumRows() {
		return Grid.rows;
	}
	
	/**
	 * Get the number of columns in the grid.
	 * 
	 * @return
	 */
	public static int getNumCols() {
		return Grid.cols;
	}
	
	/**
	 * Gets the gridpsrite at the given coordinates.
	 * @param x
	 * @param y
	 * @return
	 */
	public static GridSprite getGridSprite(int x, int y) {
		for(GridSprite gs : sprites) {
			LinkedList<Coord> coords = gs.getCoords();
			for(Coord c : coords) {
				if(c.x == x && c.y == y ) {
					return gs;
				}
			}
		}
		return null;
	}
	
	/**
	 * Spawn the player (blue) at the left edge and vertically centered on the grid.
	 */
	public void spawnPlayer() {
		int x = 0;
		int y = getNumRows()/2;
		
		player = new Player(x, y);
		player.setDirection(GridSprite.DIRECTION_EAST);
		sprites.add(player);
	}
	
	public void spawnRedEnemy(Coord coord) {
		
		sprites.add(new RedEnemy((int)coord.x, (int)coord.y));
	}
	
	public void spawnRedEnemy() {
		int x = getNumCols();
		int y = getNumRows()/2;
		
		sprites.add(new RedEnemy(x, y));
	}
	
	/**
	 * Desspawns all enemy NPCs from the grid.
	 */
	public void despawnAllEnemies() {
		
		for(GridSprite gs : sprites) {
			// Remove sprite from arraylist if it is an npc (assuming that all NPCs are enemies)
			if(gs.getType() == GridSprite.TYPE_NPC) {
				sprites.remove(gs);
			}
		}
	}
	
	/**
	 * Desspawns all enemy NPCs from the grid.
	 */
	public void despawnAllDeadEnemies(boolean addPoints) {
		
		for(GridSprite gs : sprites) {
			// Remove sprite from arraylist if it is an npc (assuming that all NPCs are enemies)
			if(gs.getType() == GridSprite.TYPE_NPC) {
				if(gs.isDead() || gs.isMovementDisabled()) {
					
					// For each cell in this dead sprite, add one point.
					DirVenGame.score += gs.getChainLength();
					
					gs.despawn();
				}
			}
		}
	}
	
	/**
	 * Setup the level
	 */
	public void setupLevel() {
		if(Level.getCurrentLevel() == 1) {
			spawnPlayer();
			spawnRedEnemy();
		} else {
			// Despawn all enemies and add points for those killed
			despawnAllDeadEnemies(true);
			
			spawnRedEnemy(genRandomCoord());
		}
		
		sprites.add(new MetalBox(28, 49));
		sprites.add(new MetalBox(28, 48));
		sprites.add(new MetalBox(28, 47));
		sprites.add(new MetalBox(28, 46));
		sprites.add(new MetalBox(28, 45));
		sprites.add(new MetalBox(28, 44));
		sprites.add(new MetalBox(28, 43));
		sprites.add(new MetalBox(28, 42));
		sprites.add(new MetalBox(28, 41));
		sprites.add(new MetalBox(28, 40));
		sprites.add(new MetalBox(28, 39));

	
		
		
		sprites.add(new MetalBox(30, 45));
		sprites.add(new MetalBox(31, 45));
		sprites.add(new MetalBox(32, 45));
		sprites.add(new MetalBox(33, 45));
		sprites.add(new MetalBox(34, 45));
		
		// Set the countdown for this level
		DirVenGame.stationCountdown = new Countdown(Level.getSurvivalTimeInStation(), 1000);
	}
	
	/**
	 * Generates a random coordinate that is on the grid and not occupied by another sprite.
	 * 
	 * @return
	 */
	public static Coord genRandomCoord() {
		
		int x = 0,
			y = 0;
		
		Coord randCoord = new Coord((float)x, (float)y);
		
		do {
			// Generate random coordinates
			randCoord.x = (float)Misc.randomNumber(0, getNumCols());
			randCoord.y = (float)Misc.randomNumber(0, getNumRows());
			
		} while (collide(randCoord) == true); // Regenerate coordinate if it is occupied.
		
		return randCoord;
	}
	
	public static Coord genRandomCoordOnWall() {
		// TODO: code the method
		int x = 0,
			y = 0;
		
		Coord randCoord = new Coord((float)x, (float)y);
		
		do {
			// Generate random coordinates
			randCoord.x = (float)Misc.randomNumber(0, getNumCols());
			randCoord.y = (float)Misc.randomNumber(0, getNumRows());
			
		} while (collide(randCoord) == true); // Regenerate coordinate if it is occupied.
		
		return randCoord;
	}
	
	/**
	 * 
	 * BitWISE OPERATOR tutorial: http://www.java2s.com/Code/Java/Language-Basics/BitwiseDemo.htm
	 */
	private void blockAllWalls() {
		// NOTE: Bitwise AND
		blockedWalls = WALL_NORTH | WALL_SOUTH | WALL_EAST | WALL_WEST;
		
		wallNorthBlocked = true;
		wallSouthBlocked = true;
		wallEastBlocked = true;
		wallWestBlocked = true;
	}
	
	/**
	 * 
	 * @param wall
	 * BitWISE OPERATOR tutorial: http://www.java2s.com/Code/Java/Language-Basics/BitwiseDemo.htm
	 */
	private void blockAllWallsExcept(byte wall) {
		// Block all walls
		blockAllWalls();
		// Unblock the one given
		blockedWalls &= ~wall; // Unset flag for this wall.
		
		if(wall == WALL_NORTH) {
			wallNorthBlocked = false;
		} else if(wall == WALL_SOUTH) {
			wallSouthBlocked = false;
		} else if(wall == WALL_EAST) {
			wallEastBlocked = false;
		} else if(wall == WALL_WEST) {
			wallWestBlocked = false;
		}
	}
	
	/**
	 * Opens up a random wall (NORTH, EAST, WEST, or SOUTH) and blocks the rest.
	 */
	public void openRandomWall() {
		int random = Misc.randomNumber(1, 4);
		
		switch(random) {
			case 0: blockAllWallsExcept(WALL_NORTH); break;
			case 1: blockAllWallsExcept(WALL_SOUTH); break;
			case 2: blockAllWallsExcept(WALL_EAST); break;
			case 3: blockAllWallsExcept(WALL_WEST); break;
		}
		/*if(Grid.wallNorthBlocked == false) {
			blockAllWallsExcept(WALL_EAST);
		} else if(Grid.wallSouthBlocked == false) {
			blockAllWallsExcept(WALL_WEST);
		} else if(Grid.wallEastBlocked == false) {
			blockAllWallsExcept(WALL_SOUTH);
		} else if(Grid.wallWestBlocked == false) {
			blockAllWallsExcept(WALL_NORTH);
		}*/
	}
	
	/**
	 * Check if the grid coordinate is collided with anything on the grid.
	 * 
	 * @param coord
	 * @return
	 * BitWISE OPERATOR tutorial: http://www.java2s.com/Code/Java/Language-Basics/BitwiseDemo.htm
	 */
	public static boolean collide(Coord coord) {
		
		boolean collided = false;
		
		// Check if collided with walls
		
		// Check if crashed into west wall (if blocked).
		if(coord.x < 0 && wallWestBlocked /*(blockedWalls & WALL_WEST) == WALL_WEST*/) {
			collided = true;
			Gdx.app.log("DIRVEN", "Collided with WEST WALL");
		// Check if collided into east wall  (if blocked).
		} else if(coord.x >= cols && wallEastBlocked /*(blockedWalls & WALL_EAST) == WALL_EAST*/) {
			collided = true;
			Gdx.app.log("DIRVEN", "Collided with EAST WALL");
		// Check if collided into north wall  (if blocked).
		} else if(coord.y >= rows && wallNorthBlocked /*(blockedWalls & WALL_NORTH) == WALL_NORTH*/) {
			collided = true;
			Gdx.app.log("DIRVEN", "Collided with NORTH WALL");
		// Check if collided into south wall  (if blocked).
		} else if(coord.y < 0 && wallSouthBlocked /*(blockedWalls & WALL_SOUTH) == WALL_SOUTH*/) {
			collided = true;
			Gdx.app.log("DIRVEN", "Collided with SOUTH WALL");
		}
		
		// Check if collided with any sprites
		for(int i = 0; i < sprites.size(); i++) {
			GridSprite sprite = sprites.get(i);
			LinkedList<Coord> spriteCoords = sprite.getCoords();
			// Check each coordinate in the sprite 
			for(int j = 0; j < sprite.getCoords().size(); j++) {
				Coord spriteCoord = spriteCoords.get(j);
				
				if(spriteCoord != null) {
					// If the given coordinate is the same as any of the coordinates 
					// in any of the sprites then there is a collision.
					if(spriteCoord.x == coord.x && spriteCoord.y == coord.y) {
						Gdx.app.log("DIRVEN", "Collide with sprite");
						return true;
					}
				}
			
			}
			
		}
		
		return collided;
	}
	
	/**
	 * Update the grid.
	 */
	public void update() {
		long currentTime = System.currentTimeMillis();
		long tDiff = currentTime - lastUpdated;
		
		// Determine input for player.
		player.determineInput();
		
		// Try to update every 250 ms
		if(tDiff < 25) {
			return;
		}
		
		if(setting == SETTING_STATION) {
			//newX++;
			//setCellValue(newX, getNumRows()/2, CELL_BLUE);
			
			// Loop through each sprite
			for(int i = 0; i < sprites.size(); i++) {
				GridSprite gs = sprites.get(i);
				
				// Update the grid sprite
				gs.update();
				
				// Dead
				if(gs.isDead()) {
					gs.destroy();
					sprites.remove(i);
				}
				
			}
			
		} else if(setting == SETTING_TUNNEL) {
			
		}
		laser.update();
		lastUpdated = currentTime;
	
	} // END update() method
	
	public boolean canDraw(GridSprite gs, Coord gridCoord, Laser laser) {
		if(laser.intersectsGridCoord(gridCoord)) {
			switch(gs.getType()) {
				case GridSprite.TYPE_REFLECT:
					gs.onReflect(laser);
				break;
			}
			
			if(gs.getType() != GridSprite.TYPE_REFLECT) {
				laser.reflectedLaser = null;
			} else {
				return true;
			}
			
			
			return false;
		} else {
			
			if(gs.isReflecting) {
				gs.isReflecting = false;
				laser.reflectedLaser = null;
			}
			return true;
		}
	}

	/**
	 * Render the grid.
	 * 
	 * @param gl
	 */
	public void render(SpriteBatch batch) {
		
		// Render the background.
		GridBackground.render(batch);
		
		// Render unblocked walls.
		WallGlow.render(batch);
		
		if(setting == SETTING_STATION) {
			
			// Draw each sprite
			for(int i = 0; i < sprites.size(); i++) {
				
				GridSprite gs = sprites.get(i);	
				
				if(gs.isVisible() == false) {
					continue;
				}
				
				LinkedList<Coord> coords = gs.getCoords();
				
				// Draw each coordinate of each sprite
				for(int j = 0; j < coords.size(); j++) {
					Coord gridCoord = coords.get(j);
					if(gridCoord != null) {
						recursive = 0;
						if(canDraw(gs, gridCoord, laser)) {
							
							// Convert the grid coordinates of the cell into pixel coordinates 
							// Set the sprite's position
							gs.setPosition(gridCoord.x * cellWidth, gridCoord.y * cellHeight);
							
							// Draw it!
							gs.draw(batch);
							
						} else {
							switch(gs.getType()) {
								case GridSprite.TYPE_PLAYER:
								case GridSprite.TYPE_NPC:
									if(gridCoord.equals(gs.getHeadPosition())) {
										gs.onKilled();
									} else {
									
										coords.remove(j);
										continue;
									}
								break;
							}
						}
						
						//if(laser.reflectedLaser != null) {
						//	drawReflectedLaser(gs, gridCoord, laser.reflectedLaser);
						//}
						
						/*if(laser.intersectsGridCoord(gridCoord)) {
							switch(gs.getType()) {
								case GridSprite.TYPE_PLAYER:
								case GridSprite.TYPE_NPC:
									if(gridCoord.equals(gs.getHeadPosition())) {
										gs.onKilled();
									} else {
									
										coords.remove(j);
										continue;
									}
								break;
								case GridSprite.TYPE_REFLECT:
									gs.onReflect(laser);
								break;
							}
							
							if(gs.getType() != GridSprite.TYPE_REFLECT) {
								laser.reflectedLaser = null;
							}
						} else {
							
							if(gs.isReflecting) {
								gs.isReflecting = false;
								laser.reflectedLaser = null;
							}
							
							// Convert the grid coordinates of the cell into pixel coordinates 
							// Set the sprite's position
							gs.setPosition(gridCoord.x * cellWidth, gridCoord.y * cellHeight);
							
							// Draw it!
							gs.draw(batch);
						}*/
						
						if(laser.reflectedLaser != null) {
							if(laser.reflectedLaser.intersectsGridCoord(gridCoord)) {
								switch(gs.getType()) {
									case GridSprite.TYPE_PLAYER:
									case GridSprite.TYPE_NPC:
										if(gridCoord.equals(gs.getHeadPosition())) {
											gs.onKilled();
										} else {
										
											coords.remove(j);
											continue;
										}
									break;
								}
							} else {
								
								// Convert the grid coordinates of the cell into pixel coordinates 
								// Set the sprite's position
								gs.setPosition(gridCoord.x * cellWidth, gridCoord.y * cellHeight);
								
								// Draw it!
								gs.draw(batch);
							}
						}
						
						
					}
				}
			}
			
		} else if(setting == SETTING_TUNNEL) {
			
		}
		
		laser.draw(batch);
		
	} // END render() method.
	
	public void finalize() {
		/*player = null;
		level = null;
		if(sprites != null) {
			sprites.clear();
		}
		sprites = null;*/
		try {
			super.finalize();
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void dispose() {
		for(int i = 0; i < sprites.size(); i++) {
			GridSprite gs = sprites.get(i);
			gs.destroy();
		}
		player.destroy();
		WallGlow.dispose();
		GridBackground.dispose();
	}
}
